﻿using System;
using System.Windows;
using System.Windows.Controls.Primitives;

namespace Notoric.View.Music.CWNotation.Controls
{
    /// <summary>
    /// Implements a drag thumb for core staff symbols.
    /// </summary>
    public class CoreSymbolDragThumb : Thumb
    {
        #region Constructors

        /// <summary>
        /// Creates a new <see cref="CoreSymbolDragThumb"/> instance.
        /// </summary>
        public CoreSymbolDragThumb()
        {
            DragStarted += new DragStartedEventHandler(CoreSymbolDragThumb_DragStarted);
            DragDelta += new DragDeltaEventHandler(CoreSymbolDragThumb_DragDelta);
            DragCompleted += new DragCompletedEventHandler(CoreSymbolDragThumb_DragCompleted);
        }

        #endregion

        #region Event Handlers

        /// <summary>
        /// Occurs when the user has finished the drag operation.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler commits the changes to the dragged symbol.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void CoreSymbolDragThumb_DragCompleted(object sender, DragCompletedEventArgs e)
        {
            if (draggedSymbol != null)
            {
                draggedSymbol.CommitChanges();
                draggedSymbol = null;
            }
        }

        /// <summary>
        /// Occurs when the user has moved the dragged symbol.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// If the drag direction is not set yet, the handler sets it to the greater
        /// of the horizontal / vertical offset.
        /// If it is set, the handler moves the symbol in the correct direction.
        /// In case of the vertical drag direction, the position change is accumulated
        /// until it reaches the distance between wo consecutive staff positions.
        /// Then the staff position of the dragged symbol is updated.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void CoreSymbolDragThumb_DragDelta(object sender, DragDeltaEventArgs e)
        {
            if (draggedSymbol != null)
            {
                if (dragDirection == DragDirection.NotSet)
                {
                    if (Math.Abs(e.VerticalChange) > Math.Abs(e.HorizontalChange))
                        dragDirection = DragDirection.Vertical;
                    else
                        dragDirection = DragDirection.Horizontal;
                }

                switch (dragDirection)
                {
                    case DragDirection.Horizontal:
                        draggedSymbol.Position += e.HorizontalChange;
                        break;
                    case DragDirection.Vertical:
                        accumulator += e.VerticalChange;
                        if (Math.Abs(accumulator) >= halfSpaceSize)
                        {
                            draggedSymbol.StaffPosition -= (int)(accumulator / halfSpaceSize);
                            accumulator = 0;
                        }
                        break;
                }
            }
        }

        /// <summary>
        /// Occurs when the user has started the drag operation.
        /// </summary>
        /// <remarks>
        /// <b>Operation:</b><br/>
        /// The handler stores the dragged symbol passed in the data context
        /// and initializes the drag direction to <see cref="DragDirection.NotSet"/>,
        /// the accumulator to 0 and the distance between two consecutive staff positions.
        /// </remarks>
        /// <param name="sender">Object where the event handler is attached</param>
        /// <param name="e">Event data</param>
        private void CoreSymbolDragThumb_DragStarted(object sender, DragStartedEventArgs e)
        {
            draggedSymbol = DataContext as ViewModel.Elements.CWNotation.CoreSymbol;
            
            if (draggedSymbol != null)
            {
                halfSpaceSize = draggedSymbol.FindOwner<ViewModel.Elements.CWNotation.Staff>().SpaceSize / 2;
                accumulator = 0;
                dragDirection = DragDirection.NotSet;
            }
        }

        #endregion

        #region Dependency Property Fields

        /// <summary>
        /// Identifies the <see cref="Text"/> dependency property.
        /// </summary>
        public static readonly DependencyProperty TextProperty = DependencyProperty.Register(
            "Text", typeof(string), typeof(CoreSymbolDragThumb));

        #endregion

        #region Public Properties

        /// <summary>
        /// Gets or sets the text that will be displayed in the thumb's text block.<br/>
        /// This is a dependency property.
        /// </summary>
        /// <value>
        /// The text that will be displayed inside the thumb's text block.
        /// </value>
        public string Text
        {
            get { return (string)GetValue(TextProperty); }
            set { SetValue(TextProperty, value); }
        }

        #endregion

        #region Private Types

        /// <summary>
        /// Defines the dragging direction.
        /// </summary>
        private enum DragDirection
        {
            /// <summary>
            /// The direction has not yet been set.
            /// </summary>
            NotSet,
            /// <summary>
            /// The symbol is dragged in the vertical direction.
            /// </summary>
            Vertical,
            /// <summary>
            /// The symbol is dragged in the horizontal direction.
            /// </summary>
            Horizontal
        }

        #endregion

        #region Private Fields

        /// <summary>
        /// Holds the accumulator for vertical dragging.
        /// </summary>
        private double accumulator;
        /// <summary>
        /// Holds the direction of dragging.
        /// </summary>
        private DragDirection dragDirection;
        /// <summary>
        /// Holds a reference to the dragged symbol.
        /// </summary>
        private ViewModel.Elements.CWNotation.CoreSymbol draggedSymbol;
        /// <summary>
        /// Holds the distance between two consecutive staff positions.
        /// </summary>
        private double halfSpaceSize;

        #endregion
    }
}
